bash中for命令的基本格式如下:
1234for var in listdocommandsdone例如:
123456#!/bin/bash# basic for commandfor test in Alabama Alaska Arizona Arkansas California Coloradodoecho The next state is $testdone1234567$ ./test1The next state is AlabamaThe next state is AlaskaThe next state is ArizonaThe next state is ArkansasThe next state is CaliforniaThe next state is Colorado在最后一次迭代后,$test变量的值会在shell脚本的剩余部分一直保持有效,它会一直保持最后一次迭代的值
- 当list列表中含有单引号时,可以使用下面两种方法中的其中一种:
- 使用转义字符(反斜线)来将单引号转义
- 使用双引号来定义用到单引号的值
- for循环假定每个值是用空格进行分隔的,此时如果单独的数据中含有空格,就必须用双引号将这些值圈起来
从变量读取列表,例如:
123456789$ cat test4.sh#!/bin/bash# using a variable to hold the listlist="Alabama Alaska Arizona Arkansas Colorado"list=$list" Connecticut"for state in $listdoecho "Have you ever visited $state?"done1234567$ ./test4.shHave you ever visited Alabama?Have you ever visited Alaska?Have you ever visited Arizona?Have you ever visited Arkansas?Have you ever visited Colorado?Have you ever visited Connecticut?注意第五行的向变量中存储的已有文本字符串尾部添加文本的一个常用方法
生成列表中所需值的另外一种方法是使用命令的输出,可以用命令替换来执行任何能产生输出的命令:
123456#!/bin/bashfile="states"for state in $(cat file)doecho "Visit beautiful $state"done123456789$ ./test5Visit beautiful AlabamaVisit beautiful AlaskaVisit beautiful ArizonaVisit beautiful ArkansasVisit beautiful ColoradoVisit beautiful ConnecticutVisit beautiful DelawareVisit beautiful Florida上面的这个例子中将各个地名放在了单独的一行,for命令会以每次一行的方式遍历cat命令的输出,但这并没有解决数据中有空格的问题,如果你列出的一行中有空格,for命令仍然会将每个单词当作单独的值。
造成这个问题的根本原因是特殊的环境变量IFS,叫做内部字段分隔符(internal field separator),IFS环境变量定义了bash shell用作字段分隔符的一系列字符,默认情况下,bash shell会将下面的字符当作字段分隔符:
- 空格
- 制表符
- 换行符
如果bash shell在数据中看到了这些字符中的任意一个,他就会假定这表明了列表中一个新数据字段的开始。当然你可以修改IFS的值,例如你可以在shell脚本中临时更改IFS环境变量的值:
IFS=$'\n'
,这样就只能识别换行符
在处理代码量较大的脚本时,可能需要在一个地方修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值,可以这样实现:
1234IFS.OLD=$IFSIFS=$'\n'<在代码中使用新的IFS值>IFS=$IFS.OLD如果要指定多个IFS字符,只要将它们在赋值行串联起来即可:
1IFS=$'\n':;"这个赋值会将换行符、冒号、分号和双引号作为字段分隔符
用通配符读取目录
1234567891011#!/bin/bashfor file in /home/rich/test/*doif [ -d "$file" ]thenecho "$file is a directory"elif [ -f "$file" ]thenecho "$file is a file"fidone该代码用test命令测试了每个条目(使用方括号方法),以查看它的目录(通过-d参数)还是文件(通过-f参数)。需要注意的是,在Linux中目录名和文件名是可以包含空格的,所以需要将
$file
使用双引号圈起来,否则会出错,在test命令中,bash shell会将额外的单词当作参数,进而造成错误。
- bash中C语言风格的循环语句:for ((variable assignment; condition; iteration process)),例如:
for (( a = 1; a < 10; a++ ))
,有以下几点需要注意:- 变量赋值可以有空格
- 条件中的变量不以美元符开头
- 迭代过程中的算式未用expr命令格式
while命令的基本格式如下:
123while test commanddodone最常见的test command的用法是用方括号来检查循环命令中用到的shell变量的值:
1234567#!/bin/bashvar1=10;while [ $var1 -gt 10 ]doecho $var1var1=$[$var1 - 1]donewhile命令允许你在while语句定义多个测试命令,只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
循环处理文件数据,通常必须遍历存储在文本中的数据,这需要使用到两种技术:
- 使用嵌套循环
- 修改IFS环境变量
通过修改IFS环境变量,就能强制for命令将文件中的每一行都当作单独的一个条目来进行处理,即使数据中有空格也是如此,一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数据。典型的例子就是处理/etc/passwd文件中的数据,首先需要遍历/etc/passwd文件,并将IFS变量的值改成冒号:
123456789101112#!/bin/bashIFS.OLD=$IFSIFS=$'\n'for entry in $(cat /etc/passwd)doecho "Values in $entry -"IFS=:for value in $entrydoecho " $value"donedone熟悉C语言的都知道break命令可以用于退出当前的循环,有时你在内部循环,但需要停止外部循环,break命令可以接受单个命令行参数值:
break n
,其中n指定了要跳出的循环层级,默认情况下n为1,表明跳出的是当前循环,如果将n设为2,break命令会停止下一级的外部循环。注意:for循环的循环条件((xxx;xxx;xxx))表达式和括号之前的空格是可有可无的,但是对于if命令的test条件判断([])方括号和表达式之间必须有空格
在shell脚本中,可以对循环的输出使用管道或进行重定向,这可以在done命令之后添加一个处理命令来实现:
12345678910#!/bin/bashfor file in /home/gtp/*doif [ -d "$file" ]thenecho "$file is a directory"elifecho "$file is a file"fidone > output.txtshell会将for命令的输出重定向到文件output.txt中,而不是在屏幕上。当然这种方法同样适用于将循环的结果管接到另一个命令,此时需要使用 | 符号,而不是 > 符号
查找可执行文件:
12345678910111213#!/bin/bashIFS=:for folder in $PATHdoecho "$folder:"for file in $folder/*doif [ -x $file ]thenecho " $file"fidonedone创建多个用户帐户:
不用为每个需要创建的新用户账户手动输入useradd命令, 而是可以将需要添加的新用户账户放在一个文件中,然后创建一个脚本来实现,这个文本文件的格式如下:
userid,user name
,两个值之间用,
分隔,这样就形成了一种名为逗号分隔符的文件格式(或者是.csv),这种文件格式在电子表格中极其常见,所以你可以轻松的在电子表格程序中创建用户账户列表,然后将其保存成.csv格式,以备shell脚本读取并处理。要读取文件中的数据,首先将IFS分隔符设置为逗号,并将其放入while语句的条件测试部分,然后使用read命令读取文件中的各行:
1while IFS=',' read -r userid nameread命令会自动读取.csv文本文件中的下一行内容,当read命令返回false时,即文件读取完毕,循环结束。
要想把数据从文件中送入while命令, 只需在while命令尾部使用一个重定向就可以了:
1234567#!/bin/bashinput="users.csv"while IFS=',' read -r userid namedoecho "adding $userid"useradd -c "$name" -m $useriddone < "$input"